package facade;

import java.lang.reflect.Method;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanPredicate;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.functors.AllPredicate;
import org.apache.commons.collections.functors.EqualPredicate;

public class Facade {
	public ArrayList<Object> ObjectList;
	private Map<String, ArrayList<String>> dependList, depsave;
	private Controleur ctrl;
	private ArrayList<String> tableDef;

	/* Instanciation d'un nouvel Objet de type 'ClassName' */
	private Object getModelObject(String ClassName) throws Exception {
		String name = "model." + ClassName;
		Class cl = Class.forName(name);
		Object newObject = cl.newInstance();
		return newObject;
	}

	/* R�cup�ration d'une classe Java en fonction d'un type SQL */
	private Class getTrueClass(String type) throws ClassNotFoundException {
		Class cl = null;
		if (type.contentEquals("INTEGER UNSIGNED")
				|| type.contentEquals("INTEGER") || type.contentEquals("INT")
				|| type.contentEquals("INT UNSIGNED"))
			cl = Integer.class;
		else if (type.contentEquals("FLOAT UNSIGNED")
				|| type.contentEquals("FLOAT"))
			cl = Float.class;
		else if (type.contentEquals("VARCHAR"))
			cl = String.class;
		else if (type.contentEquals("DATE"))
			cl = Date.class;
		return cl;
	}

	/* R�cuperation et typage d'un champ SQL selon un Classe Java */
	private Object setAttrClass(ResultSet result, Class attrClass, int x)
			throws SQLException {
		Object o;
		if (attrClass.getName() == "java.lang.Integer")
			o = new Integer(result.getInt(x));
		else if (attrClass.getName() == "java.lang.String")
			o = new String(result.getString(x));
		else if (attrClass.getName() == "java.lang.Float")
			o = result.getFloat(x);
		else if (attrClass.getName() == "java.sql.Date")
			o = result.getDate(x);
		else
			o = new Object();
		return o;
	}

	/*
	 * Attribution des Propri�t�s d'un Objet en fonction des r�sultats d'une
	 * requete SQL
	 */
	private void setObjectAttr(Object obj, ResultSet result) throws Exception {
		Method m;
		for (int x = 1; x <= result.getMetaData().getColumnCount(); x++) {
			String mName = result.getMetaData().getColumnName(x);
			if (!isInDepList(mName)) {
				Class attrClass = getTrueClass(result.getMetaData()
						.getColumnTypeName(x));
				Object attr = setAttrClass(result, attrClass, x);
				Object tabParameterValues[] = { attr };
				Class tabParameterTypes = attrClass;
				m = obj.getClass().getMethod("set" + mName, tabParameterTypes);
				m.invoke(obj, tabParameterValues);
			}
		}
	}

	/* Attribution des propri�t�s d'un objet en fonction d'une HashMap */
	private void setObjectAttr(Object obj, Map<String, Object> attr)
			throws Exception {
		Iterator<String> it = attr.keySet().iterator();
		while (it.hasNext()) {
			String field = it.next();
			invokeSetter(obj, field, attr.get(field));
		}
	}

	/* Invocation d'un getter sur un champ d'un objet */
	public Object invokeGetter(Object target, String Field) throws Exception {
		Method m = target.getClass().getMethod("get" + Field);
		return m.invoke(target);
	}

	/* Invocation d'un setter sur un champ d'un objet */
	public void invokeSetter(Object target, String Field, Object newValue)
			throws Exception {
		if (this.getClassProperties(target.getClass().getSimpleName()).contains(Field))
		{
		String cName = target.getClass().getSimpleName();
		Class tabParametersTypes = newValue.getClass();
		Object[] tabParametersValues = { newValue };
		Method m = target.getClass().getMethod("set" + Field,
				tabParametersTypes);
		m.invoke(target, tabParametersValues);
		}
	}

	public Object generateObject_L(String type, Map<String, Object> attr)
			throws Exception {
		Object newObject = this.getModelObject(type);
		setObjectAttr(newObject, attr);
		return newObject;
	}

	/* R�cup�ration d'une r�f�rence sur un Objet contenu dans un autre */
	public Object getContainedObject(Object obj, String field, int index)
			throws Exception {
		Object objList = invokeGetter(obj, field);
		return ((ArrayList<Object>) objList).get(index);
	}

	private void setObjectLink(Object obj) throws Exception {
		String cName = obj.getClass().getSimpleName();
		String depName = dependList.get(cName + "_l").get(0);
		String tabName = "";
		String cId = invokeGetter(obj, "Id").toString();
		List newList = new ArrayList<Object>();
		for (int x = 0; x < tableDef.size(); x++) {
			if (tableDef.get(x).contains(depName.toLowerCase()))
				if (tableDef.get(x).contains(depName.toLowerCase()))
					if (tableDef.get(x).contains("_link"))
						tabName = tableDef.get(x);
		}
		ctrl.resultset_2 = ctrl.statement_2.executeQuery("SELECT * FROM `"
				+ tabName + "`;");
		List dList = createObject_L(depName);
		while (ctrl.resultset_2.next()) {
			if (ctrl.resultset_2.getString(cName).contains(cId)) {
				for (int x = 0; x < dList.size(); x++)
					if (invokeGetter(dList.get(x), "Id").toString()
							.contentEquals(ctrl.resultset_2.getString(depName)))
						newList.add(dList.get(x));
			}
		}
		invokeSetter(obj, depName, newList);
	}

	/*
	 * Attribution des d�pendances d'un Objet se r�f�rant au fichier XML de
	 * configuration
	 */
	private void setObjectDependances(Object obj) throws Exception {
		String cName = obj.getClass().getSimpleName();
		if (dependList.get(cName + "_l") != null)
			setObjectLink(obj);
		else if (dependList.get(cName) != null) {
			ArrayList<String> depList = dependList.get(cName);
			String cId = invokeGetter(obj, "Id").toString();
			for (int x = 0; x < depList.size(); x++) {
				List newList = new ArrayList<Object>();
				List allList = createObject_H(depList.get(x));
				if (checkDepDirection(cName, depList.get(x)))
					while (ctrl.resultset_2.next())
						for (int y = 0; y < allList.size(); y++) {
							if (invokeGetter(allList.get(y), "Id").toString()
									.contentEquals(
											ctrl.resultset_2.getString(depList
													.get(x))))
								if (invokeGetter(obj, "Id").toString()
										.contentEquals(
												ctrl.resultset_2
														.getString("Id"))) {
									newList.add(allList.get(y));
								}
						}
				else
					while (ctrl.resultset_2.next())
						for (int y = 0; y < allList.size(); y++)
							if (invokeGetter(obj, "Id").toString()
									.contentEquals(
											ctrl.resultset_2.getString(cName)))
								if (invokeGetter(allList.get(y), "Id")
										.toString().contentEquals(
												ctrl.resultset_2
														.getString("Id"))) {
									newList.add(allList.get(y));
									break;
								}
				invokeSetter(obj, depList.get(x), newList);
			}
		}
	}

	public Boolean checkDepDirection(String cName, String depName)
			throws Exception {
		try {
			ctrl.resultset_2 = ctrl.statement_2.executeQuery("SELECT * FROM `"
					+ cName + "`;");
			if (ctrl.resultset_2.findColumn(depName) > 0)
				return (true);
			else
				throw new Exception();
		} catch (Exception e1) {
			ctrl.resultset_2 = ctrl.statement_2.executeQuery("SELECT * FROM `"
					+ depName + "`;");
			return (false);
		}
	}

	/* Cr�ation d'un Objet en Mode Lourd */
	public List createObject_H(String objectName) throws Exception {
		List newList = new ArrayList<Object>();
		ResultSet result = ctrl.GenerateRS("Select * from `" + objectName
				+ "`;");
		while (result.next()) {
			Object newObject = getModelObject(objectName);
			setObjectAttr(newObject, result);
			setObjectDependances(newObject);
			newList.add(newObject);
		}
		return newList;
	}
	
	/* Cr�ation d'un Objet en Mode L�ger */
	public ArrayList<Object> createObject_L(String objectName) throws Exception {
		ctrl.setQueryResultSet("Select * from " + "`" + objectName + "`" + ";");
		ArrayList<Object> newList = new ArrayList<Object>();
		while (ctrl.resultset.next()) {
			Object newObject = getModelObject(objectName);
			setObjectAttr(newObject, ctrl.resultset);
			newList.add(newObject);
		}
		return newList;
	}

	/* Attachement d'un Objet sur un autre */
	public void attachObject(Object target, Object obj, String objName)
			throws Exception {
		ArrayList<Object> newList = (ArrayList<Object>) invokeGetter(target,
				obj.getClass().getSimpleName());
		newList.add(obj);
		invokeSetter(target, objName, newList);
	}

	public void deleteObject(Object obj) throws Exception
	{
		String cName = obj.getClass().getSimpleName();
		String id = invokeGetter(obj, "Id").toString();
		String Query = "DELETE FROM " + cName + " WHERE `Id` = " + id + ";";
		ctrl.statement.execute(Query);
	}

	/* Mise � jour d'une donn�e dans la DB */
	public void UpdateToDB(Object target, String Field, Object newValue)
			throws Exception {
		String cName = target.getClass().getSimpleName();
		ctrl.statement.executeUpdate("UPDATE `" + cName + "` SET " + Field
				+ " = '" + newValue.toString() + "' WHERE id = "
				+ invokeGetter(target, "Id") + ";");
	}
	
	public void UpdateToDB(String type, Map<String, Object> constant, Map<String, Object> attr) throws Exception
	{
		Iterator<String> it = attr.keySet().iterator();
		String ctype = constant.keySet().iterator().next();
		String cvalue = constant.get(ctype).toString();
		while (it.hasNext())
		{
			String property = it.next();
			String value = attr.get(property).toString();
			String query = "UPDATE " + type + " SET `" + property + "` = \"" + value + "\" ";
			query += "WHERE `" + ctype + "` = \"" + cvalue + "\";";
			ctrl.statement.executeUpdate(query);
		}
	}
	
	/* Cr�ation d'un Objet java et insertion dans la DB */
	public void insertToDB(String cName, Map<String, Object> attr)
			throws Exception {
		String query = "INSERT INTO `" + cName + "` ";
		String column = "(";
		String values = "(";
		Iterator<String> it = attr.keySet().iterator();
		while (it.hasNext()) 
		{
			String field = it.next();
			System.out.println(field);
			column += "`" + field + "`";
			values += "\"" + attr.get(field) + "\"";
			if (it.hasNext()) 
			 {
		 		column += ",";
	 			values += ",";
 			 }
		}
		column += ")";
		values += ")";
		query += column + " VALUES " + values + ";";
		System.out.println(query);
		ctrl.statement.execute(query);
	}

	public void insertObject_LToDB(Object obj) throws Exception {
		String cName = obj.getClass().getSimpleName();
		System.out.println("Inserting " + cName);
		Method methods[] = obj.getClass().getMethods();
		Map<String, Object> attr = new HashMap<String, Object>();
		for (Method m : methods) 
		{
			if (m.getName().contains("get") && !m.getName().contentEquals("getClass")) 
			{
				String PropertyName = m.getName().substring(3);
				if (!dependList.get(cName).contains(PropertyName) && !this.dependList.containsKey(PropertyName))
				{
					Object value = invokeGetter(obj, PropertyName);
					attr.put(PropertyName, value);
				}
			}
		}
		if (!existsObject(obj))
		this.insertToDB(cName, attr);
	}
	
	public void insertObject_HToDB(Object obj) throws Exception
	{
	 String cName = obj.getClass().getSimpleName();
	 if (isLinkedObject(cName))
	 {
		 insertObject_LToDB(obj);
		 List deplist = (List) invokeGetter(obj, dependList.get(cName + "_l").get(0));
		 for(Object dep : deplist)
		 {
			insertObject_LToDB(dep);
		 }
		 if (!existsObject(obj))
		 insertLinkedDep(obj, dependList.get(cName + "_l").get(0));
	 }
	 else
	 {
	     if (dependList.get(cName).size() > 0)
	     {
	    	 Map<String, Object> map = this.getClassPropertiesMap(obj);
	    	 for (int x = 0; x < dependList.get(cName).size(); x++)
	    	 {
		       List deplist = (List) invokeGetter(obj, dependList.get(cName).get(x));
		       String id = "";
		       for (Object dep : deplist)
		       {
			    insertObject_HToDB(dep);
			    id = invokeGetter(dep, "Id").toString();
		       }
		     
		     map.put(dependList.get(cName).get(x), id);
		    
	    	}
	    	 if (!existsObject(obj))
			     insertToDB(cName, map);
	    }
	 }
	}


	public void insertObject(String cName, Map<String, Object> attr) throws Exception
	{
		Object check = generateObject_L(cName, attr);
		if (!existsObject(check))
			insertToDB(cName, attr);
	}
	
	private void insertLinkedDep(Object obj, String PropertyName)
			throws Exception {
		List deplist = (List) invokeGetter(obj, PropertyName);
		for (int x = 0; x < deplist.size(); x++) 
		{
			insertLink(obj, deplist.get(x));
		}
	}

	public void insertLink(Object o1, Object o2) throws Exception {
		String cName1 = o1.getClass().getSimpleName();
		String cName2 = o2.getClass().getSimpleName();
		for (String tableName : tableDef) {
			if (tableName.contains(cName1.toLowerCase())
					&& tableName.contains(cName2.toLowerCase())
					&& tableName.contains("_link")) {
				String query = "INSERT INTO `" + tableName + "`";
				query += " (`" + cName1 + "`,";
				query += "`" + cName2 + "`) ";
				query += "VALUES ";
				query += "(" + invokeGetter(o1, "Id").toString() + ",";
				query += "" + invokeGetter(o2, "Id").toString() + ");";
				System.out.println(query);
				ctrl.statement.execute(query);
				break;
			}
		}
	}

	public Boolean existsObject(Object obj) throws Exception
	{
		String cName = obj.getClass().getSimpleName();
		EqualPredicate nameEqlPredicate = new EqualPredicate(invokeGetter(obj, "Id"));
		BeanPredicate beanPredicate = new BeanPredicate("id", nameEqlPredicate);
		List objList = createObject_H(cName);
		for (Object currentObject : objList)
		{
			if (beanPredicate.evaluate(currentObject))
				return true;
		}
		return false;
	}
	
	/* R�cup�ration de la d�finition des tables de la DB */
	private ArrayList<String> getTableDefinition() {
		ArrayList list = new ArrayList<String>();
		try {
			ctrl.resultset_2 = ctrl.statement.executeQuery("Show Tables;");
			while (ctrl.resultset_2.next()) {
				list.add(ctrl.resultset_2.getString(1));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return list;
	}

	private String formatName(String name) {
		String result = name.substring(1);
		Character init = name.charAt(0);
		init = init.toUpperCase(init);
		return init + result;
	}

	private Boolean isInDepList(String Value) throws Exception {
		Iterator<String> it = dependList.keySet().iterator();
		while (it.hasNext()) {
			String iD = it.next();
			if (dependList.get(iD).contains(Value) && dependList.get(iD).size() > 0) 
			{
				return Boolean.TRUE;
			}
		}
		return Boolean.FALSE;
	}

	public Boolean isLinkedObject(String type) {
		if (dependList.containsKey(type + "_l"))
			return (true);
		else
			return (false);
	}

	private void init_dependances() throws Exception {
		for (int x = 0; x < tableDef.size(); x++) 
		{
			ResultSet keys = ctrl.dbMeta.getImportedKeys(null, null, tableDef
					.get(x));
			ArrayList<String> depEntry = new ArrayList<String>();
			if (tableDef.get(x).contains("_link")) {
				ArrayList<String> depLink = new ArrayList<String>();
				ArrayList<String> depLink2 = new ArrayList<String>();
				String d1, d2;
				keys.next();
				d1 = formatName(keys.getString(3));
				keys.next();
				d2 = formatName(keys.getString(3));
				depLink.add(d2);
				dependList.put(d1 + "_l", depLink);
				depLink2.add(d1);
				dependList.put(d2 + "_l", depLink2);
			} else {
				while (keys.next())
					depEntry.add(formatName(keys.getString(3)));
				dependList.put(formatName(tableDef.get(x)), depEntry);
			}
		}
		depsave = new HashMap<String, ArrayList<String>>(dependList);
		//checkDep();
	}

	public void checkDep() throws Exception
	{
		dependList.remove("Config");
		Iterator<String> it = dependList.keySet().iterator();
		while (it.hasNext()) 
		{
			String iD = it.next();
			if (!iD.contains("_l"))
			{
				List prop = getClassProperties(iD);
				for (String field : dependList.get(iD))
				{
					if (dependList.containsKey(field) && !prop.contains(field))
					{
						ArrayList<String> list = new ArrayList<String>();
						list.add(iD);
						dependList.put(field, list);
						dependList.put(iD, new ArrayList<String>());
					}
				}
			}
		}
	}
	
	public Map<String , Object> getClassPropertiesMap(Object obj) throws Exception
	{
	 Map <String , Object> map = new HashMap<String, Object>();
	 List<String> proplist = getClassProperties(obj.getClass().getSimpleName());
	 for (String prop : proplist)
	 {
		 if (!this.isInDepList(prop))
		 {
			 map.put(prop, invokeGetter(obj, prop));
		 }
	 }
	 
	 return map;
	}
	public List getClassProperties(String cName) throws Exception
	{
		Class c = Class.forName("model." + cName);
		List newList = new ArrayList<String>();
		Method[] m = c.getMethods();
		
		for (int x = 0; x < m.length; x++)
		{
		 if (m[x].getName().contains("set"))
			 newList.add(m[x].getName().substring(3));
		}
		return newList;
	}
	
	public Map<String, ArrayList<String>> getDependList() {
		return dependList;
	}

	public void setDependList(Map<String, ArrayList<String>> dependList) {
		this.dependList = dependList;
	}

	public Controleur getCtrl() {
		return ctrl;
	}

	public void setCtrl(Controleur ctrl) {
		this.ctrl = ctrl;
	}

	public ArrayList<String> getTableDef() {
		return tableDef;
	}

	public void setTableDef(ArrayList<String> tableDef) {
		this.tableDef = tableDef;
	}

	// a faire : Update to db depuis string type + map<String, Object>
	// Insertion complete d'un objet lourd en db
	
	public Facade() throws Exception {
		this.ObjectList = new ArrayList<Object>();
		this.ctrl = new Controleur();
		dependList = new HashMap<String, ArrayList<String>>();
		ctrl.dbConnect();
		this.tableDef = getTableDefinition();
		init_dependances();
	}
	
	public void finalize() throws Exception
	{
		ctrl.dbDisconnect();
	}
}